using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Refraction
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        #region Variables
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private Vector2 lastMouseLocation;
        private QuadRenderComponent quadRenderer;


        private CModel _cModel;
        //refraction classes
        private BasicTechnique _basicRefraction;
        private RefractionMask _refractionMask;
        private SnellsLaw _snellsLaw;

        private Effect clearRenderTargetEffect;

        private RenderTarget2D _renderTargetSceneLeft;
        private RenderTarget2D _renderTargetSceneRight;
        private RenderTarget2D _renderTargetRefractionMask;
        private RenderTarget2D _renderTargetCubeMapFace;
        private RenderTarget2D _renderTargetCubeMapFaceTemp;

        private Viewport _defaultViewport;
        private Viewport _leftViewport;
        private Viewport _rightViewport;

        private TextureCube _textureCube;
        private Color[] _faceData;
        private Matrix _defaultView;
        private Matrix _defaultProjection;
        private Matrix _cubeMapProjection;
        private Matrix[] _cubeMapMatrices = new Matrix[6];


        private SpriteFont font; 
        private KeyboardState _lastKeyboardState;
        private bool _isDynamicCubeMapping = false;
        private Keys _leftViewportMethod = Keys.D3;
        private Keys _rightViewportMethod = Keys.F3;

        #endregion


        //Ctor
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 860;
            Content.RootDirectory = "Content";

            #region Creates Game components
            Camera camera = new TargetCamera(new Vector3(10, 0,0), Vector3.Zero, this);
            Components.Add(camera);

            string skyBoxTexture = "Textures/cubemap"; 
            SkyBox sb = new SkyBox(this, "Models/skybox", "Shaders/skybox", skyBoxTexture);
            Components.Add(sb);

            _cModel = new CModel(this, new Vector3(10, 0, 0));
            _cModel.DrawOrder = 5;
            Components.Add(_cModel);

            _basicRefraction = new BasicTechnique(this, Vector3.Zero);
            _basicRefraction.DrawOrder = 2;
            Components.Add(_basicRefraction);

            _refractionMask = new RefractionMask(this, Vector3.Zero);
            _refractionMask.DrawOrder = 4;
            Components.Add(_refractionMask);

            _snellsLaw = new SnellsLaw(this, Vector3.Zero);
            _snellsLaw.DrawOrder = 3;
            Components.Add(_snellsLaw);

            

            quadRenderer = new QuadRenderComponent(this);
            Components.Add(quadRenderer);
            #endregion
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            lastMouseLocation = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);

            base.Initialize();
        }  

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("Text");

            _renderTargetSceneLeft = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height,
                false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
            _renderTargetSceneRight = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height,
                false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
            _renderTargetRefractionMask = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height,
                false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
            _renderTargetCubeMapFace = new RenderTarget2D(GraphicsDevice, 512, 512,
               false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
            _renderTargetCubeMapFaceTemp = new RenderTarget2D(GraphicsDevice, 512, 512,
               false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);

            _textureCube = new TextureCube(GraphicsDevice, 512, false, SurfaceFormat.Color); 
            _faceData = new Color[512*512];

            _defaultViewport = GraphicsDevice.Viewport;
            _leftViewport = _defaultViewport;
            _rightViewport = _defaultViewport;
            _leftViewport.Width = _defaultViewport.Width / 2;
            _rightViewport.Width = _defaultViewport.Width / 2;
            _rightViewport.X = _defaultViewport.Width/2;
            _leftViewport.Height -= 100;
            _rightViewport.Height -= 100;
            
            clearRenderTargetEffect = Content.Load<Effect>("Shaders/clearRenderTarget");

            _defaultView = Camera.view;
            _defaultProjection = Camera.projection;

            LoadCubeMatrices();
            _lastKeyboardState = Keyboard.GetState();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            #region Input
            KeyboardState currentKeyboardState = Keyboard.GetState();
            // Allows the game to exit
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            if (currentKeyboardState.IsKeyDown(Keys.C) && _lastKeyboardState.IsKeyUp(Keys.C))
                _isDynamicCubeMapping = !_isDynamicCubeMapping;
            if (currentKeyboardState.IsKeyDown(Keys.V) && _lastKeyboardState.IsKeyUp(Keys.V))
                _cModel.Visible = !_cModel.Visible;  


            Keys[] keys = Keyboard.GetState().GetPressedKeys();
            if (keys.Length != 0)
            {
                if (keys[0] == Keys.D1 || keys[0] == Keys.D2 || keys[0] == Keys.D3)
                    _leftViewportMethod = keys[0];
                if (keys[0] == Keys.F1 || keys[0] == Keys.F2 || keys[0] == Keys.F3)
                    _rightViewportMethod = keys[0];
            }

            _lastKeyboardState = currentKeyboardState;
            #endregion     

            float dist = Math.Abs(Vector3.Distance(_cModel.Position, Camera.position));
            float dist2 = Math.Abs(Vector3.Distance(Vector3.Zero, Camera.position));
            if(dist > dist2)
                _cModel.DrawOrder = 1;
            else
                _cModel.DrawOrder = 5;


            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            #region clear rendertargets
            GraphicsDevice.SetRenderTargets(_renderTargetSceneLeft, _renderTargetRefractionMask);
            clearRenderTargetEffect.Techniques[0].Passes[0].Apply();
            quadRenderer.Render(Vector2.One * -1, Vector2.One);
            GraphicsDevice.SetRenderTargets(null);

            GraphicsDevice.SetRenderTargets(_renderTargetCubeMapFace);
            clearRenderTargetEffect.Techniques[0].Passes[0].Apply();
            quadRenderer.Render(Vector2.One * -1, Vector2.One);
            GraphicsDevice.SetRenderTargets(null);

            GraphicsDevice.SetRenderTargets(_renderTargetCubeMapFaceTemp);
            clearRenderTargetEffect.Techniques[0].Passes[0].Apply();
            quadRenderer.Render(Vector2.One * -1, Vector2.One);
            GraphicsDevice.SetRenderTargets(null);
            #endregion        

            _defaultView = Camera.view;

            GraphicsDevice.Clear(Color.White);
            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default; 

            #region creates the dynamic cube map

            _snellsLaw.Visible = false;
            Camera.projection = _cubeMapProjection;
            for (int i = 0; i < _cubeMapMatrices.Length; i++)
            {
                Camera.view = _cubeMapMatrices[i];

                #region Catch scene
                GraphicsDevice.Clear(Color.White);
                GraphicsDevice.SetRenderTargets(_renderTargetCubeMapFaceTemp);

                base.Draw(gameTime);

                GraphicsDevice.SetRenderTargets(null);
                #endregion

                #region Flip the rendertarget horizontal
                GraphicsDevice.Clear(Color.White);
                GraphicsDevice.SetRenderTargets(_renderTargetCubeMapFace);
                spriteBatch.Begin();
                spriteBatch.Draw(_renderTargetCubeMapFaceTemp, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height),
                                    new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White, 0,
                                    Vector2.Zero, SpriteEffects.FlipHorizontally, 1);
                spriteBatch.End();
                GraphicsDevice.SetRenderTargets(null);
                #endregion

                _renderTargetCubeMapFace.GetData<Color>(_faceData);
                _textureCube.SetData<Color>((CubeMapFace)i, _faceData);      
            }

            if(_isDynamicCubeMapping)
                _snellsLaw.DynamicCube = _textureCube;
            else
                _snellsLaw.DynamicCube = null;
            #endregion    


            #region render scene with original camera view and projection

            #region LeftRT
            Camera.view = _defaultView;
            Camera.projection = _defaultProjection;

            if (_leftViewportMethod == Keys.D3 )
                _snellsLaw.Visible = true;  
            else
                _snellsLaw.Visible = false;

            GraphicsDevice.Clear(Color.Black);
            GraphicsDevice.SetRenderTargets(_renderTargetSceneLeft, _renderTargetRefractionMask); 

            base.Draw(gameTime);

            GraphicsDevice.SetRenderTargets(null);
            #endregion

            #region RightRT
            if (_rightViewportMethod == Keys.F3)
                _snellsLaw.Visible = true;
            else
                _snellsLaw.Visible = false;

            GraphicsDevice.Clear(Color.Black);
            GraphicsDevice.SetRenderTargets(_renderTargetSceneRight, _renderTargetRefractionMask);

            base.Draw(gameTime);

            GraphicsDevice.SetRenderTargets(null);
            #endregion
            #endregion


            GraphicsDevice.Clear(Color.Black);

            #region LeftViewport
            GraphicsDevice.Viewport = _leftViewport;
            DrawBackgroundScene(_renderTargetSceneLeft);  

            switch (_leftViewportMethod)
            {
                case Keys.D1:
                    _snellsLaw.Visible = false; 
                    _basicRefraction.Scene = _renderTargetSceneLeft;
                    _basicRefraction.IsDraw = true;
                    base.Draw(gameTime);

                    DrawRefractionScale();
                    _basicRefraction.IsDraw = false;
                    break;
                case Keys.D2:
                    _snellsLaw.Visible = false;
                    _refractionMask.Scene = _renderTargetSceneLeft;
                    _refractionMask.AlphaMask = _renderTargetRefractionMask;
                    _refractionMask.IsDrawingRefraction = true;
                    base.Draw(gameTime);

                    DrawRefractionScale();                                                
                    DrawAlphaMask();
                    _refractionMask.IsDrawingRefraction = false;
                    break;
                case Keys.D3: 
                    DrawIndexOfRefraction();
                    break;
            }
            #endregion   

            #region RightViewport

            GraphicsDevice.Viewport = _rightViewport;
            DrawBackgroundScene(_renderTargetSceneRight);     
            
            switch (_rightViewportMethod)
            {
                case Keys.F1:
                    _snellsLaw.Visible = false; 
                    _basicRefraction.Scene = _renderTargetSceneRight;
                    _basicRefraction.IsDraw = true;
                    base.Draw(gameTime);

                    DrawRefractionScale();
                    _basicRefraction.IsDraw = false;
                    break;
                case Keys.F2:
                    _snellsLaw.Visible = false;
                    _refractionMask.Scene = _renderTargetSceneRight;
                    _refractionMask.AlphaMask = _renderTargetRefractionMask;
                    _refractionMask.IsDrawingRefraction = true;
                    base.Draw(gameTime);

                    DrawRefractionScale();                                                
                    DrawAlphaMask();
                    _refractionMask.IsDrawingRefraction = false;
                    break;
                case Keys.F3:
                    DrawIndexOfRefraction();
                    break;
            }

            #endregion       

            GraphicsDevice.Viewport = _defaultViewport;
            DrawInfo();
        }


        private void DrawBackgroundScene(RenderTarget2D renderTaregt)
        {
            spriteBatch.Begin();
            spriteBatch.Draw(renderTaregt, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
            spriteBatch.End();
        }

        #region Infotext
        private void DrawRefractionScale()
        {
            spriteBatch.Begin();
            spriteBatch.DrawString(font, "RefractionScale: "+ Actor.RefractionScale.ToString("F"), new Vector2(10, 10), Color.Black);
            spriteBatch.End();
        }

        private void DrawIndexOfRefraction()
        {
            spriteBatch.Begin();
            spriteBatch.DrawString(font, "IOR: " + Actor.IOR.ToString("F"), new Vector2(10, 10), Color.Black);
            spriteBatch.End();
        }  
  
        private void DrawAlphaMask()
        {
            spriteBatch.Begin();
            spriteBatch.Draw(_renderTargetRefractionMask, new Rectangle(GraphicsDevice.Viewport.Width-200, GraphicsDevice.Viewport.Height-200, 200,200), Color.White);
            spriteBatch.End();
        }

        private void DrawInfo()
        {
            spriteBatch.Begin();
            spriteBatch.DrawString(font, "RefractionMethods:", new Vector2(10, GraphicsDevice.Viewport.Height - 100), Color.White);
            spriteBatch.DrawString(font, "LeftViewport: 1 - 2 - 3", new Vector2(10, GraphicsDevice.Viewport.Height - 80), Color.White);
            spriteBatch.DrawString(font, "RightViewport: F1 - F2 - F3", new Vector2(10, GraphicsDevice.Viewport.Height - 60), Color.White);

            spriteBatch.DrawString(font, "+/-: RefractionScale/IOR", new Vector2(450, GraphicsDevice.Viewport.Height - 100), Color.White);
            spriteBatch.DrawString(font, "V: Disable Metallic Sphere", new Vector2(450, GraphicsDevice.Viewport.Height - 80), Color.White);  
           

            spriteBatch.DrawString(font, "Snell's Law Modi:", new Vector2(900, GraphicsDevice.Viewport.Height - 100), Color.White);
            spriteBatch.DrawString(font, "HLSL Refraction Method: H", new Vector2(900, GraphicsDevice.Viewport.Height - 80), Color.White);
            spriteBatch.DrawString(font, "Dynamic/Static CubeMap: C", new Vector2(900, GraphicsDevice.Viewport.Height - 60), Color.White);

            spriteBatch.DrawString(font, "(1/F1: SimpleRefraction), (2/F2: AlphaMaskRefraction), (3/F3: Snell's Law)", new Vector2(10, GraphicsDevice.Viewport.Height - 40), Color.White);
            spriteBatch.End();
        }
        #endregion  

        /// <summary>
        /// Creates the view and projection matrices to render the cubemap faces
        /// </summary>
        private void LoadCubeMatrices()
        {
            _cubeMapProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1, 0.1f, 10000f);

            Vector3 position = Vector3.Zero;

            _cubeMapMatrices[0] = Matrix.CreateLookAt(position, position + Vector3.UnitX, Vector3.Up);
            _cubeMapMatrices[1] = Matrix.CreateLookAt(position, position - Vector3.UnitX, Vector3.Up);
            _cubeMapMatrices[2] = Matrix.CreateLookAt(position, position + Vector3.UnitY, -Vector3.UnitZ);//top
            _cubeMapMatrices[3] = Matrix.CreateLookAt(position, position - Vector3.UnitY, Vector3.UnitZ);//bottom  
            _cubeMapMatrices[4] = Matrix.CreateLookAt(position, position + Vector3.UnitZ, Vector3.Up);
            _cubeMapMatrices[5] = Matrix.CreateLookAt(position, position - Vector3.UnitZ, Vector3.Up);                       
        }
    }
}
